home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
PC Format (UK) 75
/
PC Format 75 - Nov 1997.iso
/
delpac
/
PAC.~PA
< prev
next >
Wrap
Text File
|
1997-08-11
|
16KB
|
658 lines
{
Maze Game
John Kennedy 5/8/97
Featuring use of sprites, reading the keyboard,
and anything else which springs to mind. Should
feature sound as well and animation too if I could
be bothered...
}
unit Pac;
interface
uses
SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
Forms, Dialogs, ExtCtrls, StdCtrls, Buttons;
type
TForm1 = class(TForm)
Up: TBitBtn;
Timer1: TTimer;
down: TButton;
Left: TButton;
Right: TButton;
ScoreText: TLabel;
LivesText: TLabel;
Hightext: TLabel;
procedure Timer1Timer(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure FormPaint(Sender: TObject);
procedure downClick(Sender: TObject);
procedure UpClick(Sender: TObject);
procedure LeftClick(Sender: TObject);
procedure RightClick(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
x,y,dx,dy:integer;
x1,y1,dx1,dy1:integer;
OriginalBitMap: TBitmap; {Stores the maze graphics as new }
StoreBitMap: TBitmap; {Stores the maze graphics }
BufferBitMap: TBitmap; { draw here when working stuff out }
GhostBitmap: TBitmap; { Store the pacman }
GhostMaskBitmap: TBitmap; { Store the pacman mask}
PacBitmap: TBitmap; { Store the pacman }
PacMaskBitmap: TBitmap; { Store the pacman mask}
PillBitmap: TBitmap; { Store the pill graphic }
PillMaskBitmap: TBitmap; { Store the pill mask}
maze: array[1..13] of string;
pills: array[1..13] of string;
ghostx: array[1..4] of integer;
ghosty: array[1..4] of integer;
ghostdir: array[1..4] of integer;
pac_x,pac_y,pac_dir,next_pac_dir:integer;
last_offset:integer;
score,high,lives: integer;
implementation
{$R *.DFM}
procedure UpdateGhost (nx,ny,ox,oy,ghost_dir:integer);
var
fromrect,torect,newrect:TRect;
offset:integer;
{
This is our sprite routine. It copies a graphic to
the screen without destroying what's underneath or
the surroundings.
1. Copy the underlying graphic to the buffer
2. Combine the mask with the buffer (AND)
3. Combine the graphic with the buffer (OR)
4. Copy the buffer to the screen.
Now, to erase the graphic we can simply copy the
background from the store to the screen, or at
least the portion which needs it. However, this
tends to cause flickering.
Instead, when we move the graphic, we calculate
the total shape which needs to be copied in order
to both draw the new position AND erase the old
position. This is cunning: it means we don't need
a separate erase routine and it reduces flicker.
We use a Delphi function to caclulate the "union"
between two rectangles -- the old sprite position,
and the new one. This gives us the rectangle to
work with. Neat, huh?
}
begin
{
1. Restore where it was from Store to Buffer
2. Create new image in the Buffer
3. Combine these two rectangles
4. Draw new position (which erases old) onto screen
}
case ghost_dir of
2: offset:=0;
8: offset:=32;
4:offset:=64;
1:offset:=96
end;
{ Copy from store to buffer }
fromrect := Rect(ox,oy,ox+31,oy+31);
torect := Rect(ox,oy,ox+31,oy+31);
BufferBitmap.Canvas.CopyMode := cmSrcCopy;
BufferBitmap.Canvas.CopyRect(torect,StoreBitmap.canvas, fromrect);
{ AND the buffer and the mask }
fromrect := Rect(0,0,31,31);
torect := Rect(nx,ny,nx+31,ny+31);
BufferBitmap.Canvas.CopyMode := cmSrcAnd;
BufferBitmap.Canvas.CopyRect(torect,GhostMaskBitmap.canvas, fromrect);
{ OR the buffer and the graphic }
fromrect := Rect(offset,0,offset+31,31);
BufferBitmap.Canvas.CopyMode := cmSrcPaint;
BufferBitmap.Canvas.CopyRect(torect,GhostBitmap.canvas, fromrect);
{ Calculate the entire area (old/new) to copy to screen }
fromrect := Rect(ox,oy,ox+31,oy+31);
torect := Rect(nx,ny,nx+31,ny+31);
UnionRect(newrect,fromrect,torect);
{ Copy it to the screen }
Form1.Canvas.CopyMode := cmSrcCopy;
Form1.Canvas.CopyRect(newrect,BufferBitmap.canvas, newrect);
end;
procedure DrawPill (x,y:integer);
var
fromrect,torect:TRect;
begin
x:=(x*32)-16;
y:=(y*32)-16;
{ Like a miniature sprite routine, except of course
that pills don't move }
torect := Rect(x,y,x+24,y+24);
fromrect := Rect(0,0,24,24);
BufferBitmap.Canvas.CopyMode := cmSrcCopy;
BufferBitmap.Canvas.CopyRect(torect,StoreBitmap.canvas,torect);
BufferBitmap.Canvas.CopyMode := cmSrcAnd;
BufferBitmap.Canvas.CopyRect(torect,PillMaskBitmap.canvas,fromrect);
BufferBitmap.Canvas.CopyMode := cmSrcPaint;
BufferBitmap.Canvas.CopyRect(torect,PillBitmap.canvas,fromrect);
StoreBitmap.Canvas.CopyMode := cmSrcCopy;
StoreBitmap.Canvas.CopyRect(torect,BufferBitmap.canvas,torect);
end;
procedure ErasePill (x,y:integer);
var
therect:TRect;
begin
x:=(x*32)-16;
y:=(y*32)-16;
therect := Rect(x,y,x+24,y+24);
StoreBitmap.Canvas.CopyMode := cmSrcCopy;
StoreBitmap.Canvas.CopyRect(therect,OriginalBitmap.canvas,therect);
end;
procedure UpdatePac (nx,ny,ox,oy:integer);
var
fromrect,torect,newrect:TRect;
offset:integer;
begin
{
Locate start of pacman graphic in bitmap.
Slight complication here: the pacman has four positions,
and they are all stored in the one bitmap. However, as
I created the bitmap, I know exactly where the positions
are -- they are all 32 pixels across. We can therefore use
this case/of function to assign the start position to the
variable called offset. We also need to
make sure the correct mask is used too!
}
case pac_dir of
0: offset:=last_offset;
2: offset:=0;
8: offset:=32;
4:offset:=64;
1:offset:=96
end;
last_offset:=offset;
fromrect := Rect(ox,oy,ox+31,oy+31);
torect := Rect(ox,oy,ox+31,oy+31);
BufferBitmap.Canvas.CopyMode := cmSrcCopy;
BufferBitmap.Canvas.CopyRect(torect,StoreBitmap.canvas, fromrect);
fromrect := Rect(offset,0,offset+31,31);
torect := Rect(nx,ny,nx+31,ny+31);
BufferBitmap.Canvas.CopyMode := cmSrcAnd;
BufferBitmap.Canvas.CopyRect(torect,PacMaskBitmap.canvas, fromrect);
BufferBitmap.Canvas.CopyMode := cmSrcPaint;
BufferBitmap.Canvas.CopyRect(torect,PacBitmap.canvas, fromrect);
fromrect := Rect(ox,oy,ox+31,oy+31);
torect := Rect(nx,ny,nx+31,ny+31);
UnionRect(newrect,fromrect,torect);
Form1.Canvas.CopyMode := cmSrcCopy;
Form1.Canvas.CopyRect(newrect,BufferBitmap.canvas, newrect);
end;
procedure MoveGhost(var ghost_x,ghost_y,ghost_dir:integer);
var
xx,yy,gx,gy:integer;
dir: char;
newway,free:integer;
testrect,pacrect,ghostrect: trect;
Begin
{ Oooh, horrible! Trying to make an intelligent ghost is
very difficult. Here I settle for making sure the ghost is
not moving anywhere it shouldn't. Everytime we come to a junction
(we can tell because we have a special array too look at -- it's
entirely separate from the graphics) it decides randomly to move,
or moves if it has to. It does not back-up the way it came, and
as there are no dead-ends in the maze, this is perfectly ok.
Remember this if you design your own maze! }
xx:=ghost_x;yy:=ghost_y;
case ghost_dir of
1: ghost_y:=ghost_y-4;
2: ghost_x:=ghost_x+4;
4: ghost_y:=ghost_y+4;
8: ghost_x:=ghost_x-4;
end;
UpdateGhost(ghost_x,ghost_y,xx,yy,ghost_dir);
pacrect:=Rect(pac_x,pac_y,pac_x+31,pac_y+31);
ghostrect:=Rect(ghost_x,ghost_y,ghost_x+31,ghost_y+31);
if (intersectrect(testrect,pacrect,ghostrect)<>0) then
Form1.canvas.textout(10,200,'Dead!');
{ Yes, well, ran out of time at this point. I'm sure you
can write your own getting killed routine }
if ((ghost_x-16) mod 32 = 0) and ((ghost_y-16) mod 32 = 0) then
begin
{ At a maze junction }
gx:=1+(ghost_x-16) div 32;
gy:=1+(ghost_y-16) div 32;
dir:=maze[gy][gx];
free:=strtoint('$'+dir);
if ((free and ghost_dir) = 0) or (random(10)<5) then
begin
{ Cannot carry on in current direction, get a new one }
repeat
{ Repeat until a random direction is found which is both legal,
and not back the way it came. That's what the ghost_dir plus newway
tests are about : it's a bit of a hack, but says four conditions }
case random(4) of
0: newway:=1;
1: newway:=2;
2: newway:=4;
3: newway:=8;
end;
until ((free and newway) <>0) and ((ghost_dir + newway) <>5) and ((ghost_dir + newway) <>10);
ghost_dir := newway;
end;
end;
end;
procedure MovePac;
var
xx,yy,px,py:integer;
dir: char;
newway,free:integer;
Begin
{ This is easier -- it's up to the player to
move the thing. We just wait until we get to
a junction, and see if we need to change the
direction }
xx:=pac_x;yy:=pac_y;
case pac_dir of
1: pac_y:=pac_y-4;
2: pac_x:=pac_x+4;
4: pac_y:=pac_y+4;
8: pac_x:=pac_x-4;
end;
UpdatePac(pac_x,pac_y,xx,yy);
if ((pac_x-16) mod 32 = 0) and ((pac_y-16) mod 32 = 0) then
begin
{ At a maze junction }
px:=1+(pac_x-16) div 32;
py:=1+(pac_y-16) div 32;
dir:=maze[py][px];
free:=strtoint('$'+dir);
{ If the next_pac_dir and free are in agreement, change
If they are not, check that the pac_dir and free are ok
If they are not, stop the Pacman. }
if (next_pac_dir and free) <>0 then pac_dir:=next_pac_dir
else if (pac_dir and free) = 0 then pac_dir:=0;
{ Check for pills }
if (pills[py][px]='*') then
begin
pills[py][px]:=' ';
ErasePill(px,py);
score:=score+10
end;
end;
end;
procedure TForm1.Timer1Timer(Sender: TObject);
var
i:integer;
begin
{ This is the heart of the game. Every tick of the
clock, we move the ghosts, move the player and
update the score. }
for i:=1 to 4 do
MoveGhost(ghostx[i],ghosty[i],ghostdir[i]);
MovePac;
ScoreText.caption:='Score:'+inttostr(score);
end;
procedure TForm1.FormCreate(Sender: TObject);
var
i:integer;
begin
{ This procedure is called when the games starts,
because Delphi always calls it when a form is
created. This gives us a perfect opportunity to
sort out all the things that need doing once,
such as defining and loading graphics, and
defining the special array which contains a
map of the maze. }
Randomize;
StoreBitmap := TBitmap.Create;
StoreBitmap.LoadFromFile('c:\sprite\grid5.bmp');
OriginalBitmap := TBitmap.Create;
OriginalBitmap.LoadFromFile('c:\sprite\grid5.bmp');
BufferBitmap := TBitmap.Create;
BufferBitmap.LoadFromFile('c:\sprite\grid5.bmp');
PacBitmap := TBitmap.Create;
PacBitmap.LoadFromFile('c:\sprite\newpac.bmp');
PacMaskBitmap := TBitmap.Create;
PacMaskBitmap.LoadFromFile('c:\sprite\newpmask.bmp');
GhostBitmap := TBitmap.Create;
GhostBitmap.LoadFromFile('c:\sprite\ghost.bmp');
GhostMaskBitmap := TBitmap.Create;
GhostMaskBitmap.LoadFromFile('c:\sprite\gostmask.bmp');
PillBitmap := TBitmap.Create;
PillBitmap.LoadFromFile('c:\sprite\dot.bmp');
PillMaskBitmap := TBitmap.Create;
PillMaskBitmap.LoadFromFile('c:\sprite\dotmask.bmp');
pac_x:=16+(32*7);
pac_y:=16+(32*8);
pac_dir:=0;
next_pac_dir:=0;
score:=0;
high:=0;
lives:=3;
for i:=1 to 4 do
begin
ghostdir[i]:=2;
ghostx[i]:=16;
ghosty[i]:=16;
end;
{ Now, this is tricky. This is a map of the maze, one letter/number
for every junction. It tells the ghosts and player which ways they can
go. Here's how the letter/number is defined. Using
1=Up, 2=Right, 4=Down, 8=Left
add up all the directions which are possible. As this adds up to
a maximum of 15 for all directions, make this hexidecimal to
take up only one characted. This makes is an F. }
maze[1]:='6AEAAAEAAEAAEAAAEAC';
maze[2]:='5050005005005000505';
maze[3]:='5050005005005000505';
maze[4]:='5050005005005000505';
maze[5]:='7ABAEAFAABAAFAEABAD';
maze[6]:='5000505000005050005';
maze[7]:='7AAAD07AEAEAD07AAAD';
maze[8]:='5000505050505050005';
maze[9]:='7AAABAFABABAFABAAAD';
maze[10]:='5000005000005000005';
maze[11]:='7AAAC07AEAEAD06AAAD';
maze[12]:='5000505050505050005';
maze[13]:='3AAABABA903ABABAAA9';
end;
procedure TForm1.FormPaint(Sender: TObject);
var
fromrect,torect:TRect;
i,j:integer;
begin
{ This procedure is called whenever Delphi wants to draw
or update the contents of the form. This means at the very
start, so we can include code here which draws the maze.
Drawing the maze is really a matter of copying the graphics
from the Store to the visible bitmap. There is one extra trick:
the pills are drawn into the store bitmap to start with, and
erased from it using yet another bitmap to store the original,
virgin maze. }
{ Draw in pills }
for i:=1 to 13 do
for j:=1 to 19 do
if (maze[i][j]<>'0') then
begin
DrawPill(j,i);
pills[i][j]:='*';
end;
fromrect := Rect(0,0,399,299);
torect := Rect(50,20,50+399,20+299);
Form1.Canvas.CopyMode := cmSrcCopy;
Form1.Canvas.CopyRect(Rect(0,0,639,479), StoreBitmap.Canvas, Rect(0,0,639,479));
end;
{ These procedures happen when the buttons are pressed. Buttons?
Yes, there are four -- one for every direction. They just don't appear
on the screen, but we use the fact that Delphi allows hotkeys to make
it possible to steer the pacman. Yes, yet more cunning. }
procedure TForm1.downClick(Sender: TObject);
begin
{ Try to move down }
if (pac_dir=1) then pac_dir:=4;
next_pac_dir:=4;
end;
procedure TForm1.UpClick(Sender: TObject);
begin
{ Try to move up }
if (pac_dir=4) then pac_dir:=1;
next_pac_dir:=1;
end;
procedure TForm1.LeftClick(Sender: TObject);
begin
{ Try to go left }
if (pac_dir=2) then pac_dir:=8;
next_pac_dir:=8;
end;
procedure TForm1.RightClick(Sender: TObject);
begin
{ Try to go right }
if (pac_dir=8) then pac_dir:=2;
next_pac_dir:=2;
end;
end.
{
And that's it! the End!
I hope you enjoyed our little jaunt with Delphi. I hope you
can see that it's a very powerful programming language,
and is capable of almost everything. Yes, it's a database
language, but it's a whole lot more besides. You can control
multimedia files and create huge and reliable projects of
professional quality. And even better, this was free! To
my mind, this has been the best magazine give-away ever: a
full, honest to goodness PC development suite. Heck, you could
write a killer program and become rich with this. Hmm, now
that sounds a good idea... I have my eye on a new motorbike...
I've certainly found it an eye-opener when it comes to programming
the PC, as it hides all the messy stuff and lets you get on with it.
In fact, I'm almost tempted to rush out and buy the full version of
the latest v3 release (gasp! magazine writer buys software shocker!)
because it's faster and will let me use DirectX. There is also a
games creation library available which makes it even easier.
I'll put a link to this on my home web site. Please pay it a
visit if you want to ask a question, or find a link to other
sites where you'll find information from people who actually
know what they are talking about. My web site address is most
definitely here at:
http://freespace.virgin.net/john.kennedy
Appologies once again for the wrong addresses which have
sneaked out. It was all my fault entirely, and I can only
grovel for your forgiveness.
So, the end is really here. Thanks to all who have emailed
and visited the web site. I'll try and put up the previous
tutorials, copyright permitting, and any more hints and tips.
Take my advice and get programming. I want to see your games
and applications appearing on the PC Format coverdisk. There
just aren't enough home-grown, bedroom, DIY programmers. With
Delphi, you have no excuse (other than not understanding the
tutorials of course) so get to it! Program! Program! Program!
John
10/7/98
}